﻿using Bit.Art.Api.Controllers;
using Bit.Art.Db;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;
using System.Linq;
using Bit.Art.Distributed;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Bit.Art.Core.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using System.IO;

namespace bit.art.test
{
#if UNITTEST
    [TestClass]
    public class 交易API测试
    {
        private static Memcache s_Memcache;
        internal static IConfigurationRoot Configuration { get; set; }

        private static void InitializeConfiguration()
        {
            var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");
            Configuration = builder.Build();
            Utility.Configuration = Configuration;
            ApplicationLogging.LoggerFactory.AddConsole(Configuration.GetSection("Logging"));
        }

        [ClassInitialize]
        public static void 启动消息队列各个节点(TestContext context)
        {
            InitializeConfiguration();
            ApplicationLogging.LoggerFactory.AddConsole().AddDebug(LogLevel.Information);
            s_Memcache = Memcache.Create("localhost");

            try
            {
                Bit.Art.MySqlDb.Program.Setup();
                var services = new ServiceCollection();
                services.AddDbContext<ExchangeDb>(options =>
                    options.UseMySql(Configuration.GetConnectionString("ExchangeDb")), ServiceLifetime.Transient);
                services.AddDbContext<ExchangeConfigDb>(options =>
                    options.UseMySql(Configuration.GetConnectionString("ExchangeConfigDb")), ServiceLifetime.Transient);
                Bit.Art.Engine.Program.GlobalServiceProvider =
                Bit.Art.MySqlDb.Program.GlobalServiceProvider =
                Bit.Art.Bookeeper.Program.GlobalServiceProvider =
                Bit.Art.Account.Program.GlobalServiceProvider = services
                    .AddLogging()
                    .BuildServiceProvider();

                Bit.Art.Broker.Program.StartEngine();
                Bit.Art.Account.Program.StartEngine(s_Memcache);
                Bit.Art.Bookeeper.Program.StartEngine(s_Memcache);
                Bit.Art.Engine.Program.StartEngine(s_Memcache, 500, 100000);
                Bit.Art.MySqlDb.Program.StartEngine();
                // 等待1秒，确保所有节点都启动了
                Bit.Art.Broker.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.Account.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.Engine.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.MySqlDb.Program.s_autoResetEventForTest.WaitOne(1000);
            }
            catch
            {
                using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
                {
                    db.Database.ExecuteSqlCommand("DELETE FROM exchangedb.orders WHERE Id <> ''; " +
                        "DELETE FROM exchangedb.accountbalances WHERE Id <> '';");
                }
            }
        }

        [TestInitialize]
        public async Task TestInitialize()
        {
            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                DbInitializer.Intialize(db);
            }
            await s_Memcache.FlushDb();
            Bit.Art.Account.Program.LoadDataToCache(s_Memcache);
        }

        [TestCleanup]
        public void TestCleanup()
        {
            Bit.Art.Broker.Program.s_autoResetEventForTest.Reset();
            Bit.Art.Account.Program.s_autoResetEventForTest.Reset();
            Bit.Art.Bookeeper.Program.s_autoResetEventForTest.Reset();
            Bit.Art.Engine.Program.s_autoResetEventForTest.Reset();
            Bit.Art.MySqlDb.Program.s_autoResetEventForTest.Reset();

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                db.Database.ExecuteSqlCommand("DELETE FROM exchangedb.orders WHERE Id <> ''; " +
                    "DELETE FROM exchangedb.accountbalances WHERE Id <> '';");
            }
        }

        [TestMethod]
        public async Task 获取用户的挂单()
        {
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var response = await controller.BuyLimit("BTC/USDT", 1000, 1);
            Assert.IsTrue(response.Success);
            Assert.IsFalse(string.IsNullOrWhiteSpace(response.Result));
            Assert.IsTrue(Guid.TryParse(response.Result, out Guid orderid));

            Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            var actual = openOrdersResponse.Result;
            Assert.AreEqual(1, actual.Length);
            Assert.AreEqual(response.Result, actual[0].Id);
            Assert.AreEqual(1000, actual[0].Price);
            Assert.AreEqual(1, actual[0].Quantity);
            Assert.AreEqual(1, actual[0].QuantityRemaining);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == orderid);
                Assert.IsNotNull(order);
                Assert.IsFalse(order.IsCancelled);
                Assert.AreEqual(1000, order.Price);
                Assert.AreEqual(1, order.Volume);
                Assert.AreEqual(1, order.VolumeRemaining);

                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE, balance.Balance);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE, balance.Available);
                Assert.AreEqual(0, balance.Pending);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE, balance.Balance);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1000 * 1.001m, balance.Available);
                Assert.AreEqual(1000 * 1.001m, balance.Pending);
            }

            // 取消挂单，这个挂单应该获取不到了
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var cancelResponse = await controller.Cancel("BTC/USDT", actual[0].Id);
            Assert.IsTrue(cancelResponse.Success);

            Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);
            Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == orderid);
                Assert.IsNotNull(order);
                Assert.IsTrue(order.IsCancelled);

                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE, balance.Balance);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE, balance.Available);
                Assert.AreEqual(0, balance.Pending);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE, balance.Balance);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE, balance.Available);
                Assert.AreEqual(0, balance.Pending);
            }
        }

        [TestMethod]
        public async Task 限价单匹配()
        {
            decimal bidprice = 1001m, bidvolume = 1m;
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidRes = await controller.BuyLimit("BTC/USDT", bidprice, bidvolume);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes = await controller.SellLimit("BTC/USDT", bidprice, bidvolume);

            Assert.IsTrue(Guid.TryParse(bidRes.Result, out Guid bidid));
            Assert.IsTrue(Guid.TryParse(askRes.Result, out Guid askid));
            Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);
            Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            var actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == askid);
                Assert.IsNotNull(order);
                Assert.AreEqual(0, order.VolumeRemaining);
                Assert.AreEqual(bidvolume, order.Volume);
                Assert.AreEqual(bidprice, order.Price.Value);

                order = db.Orders.SingleOrDefault(o => o.Id == bidid);
                Assert.IsNotNull(order);
                Assert.AreEqual(0, order.VolumeRemaining);
                Assert.AreEqual(bidvolume, order.Volume);
                Assert.AreEqual(bidprice, order.Price.Value);

                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1001m * 0.999m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1001m * 0.999m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1, balance.Balance);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1001m * 1.001m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1001m * 1.001m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1, balance.Balance);
            }

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var apiBalancesRes = await controller.GetBalance();
            var apiBalances = apiBalancesRes.Result;
            var apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1001m * 0.999m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1001m * 0.999m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1, apiBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1001m * 1.001m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1001m * 1.001m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1, apiBalance.Balance);
        }

        [TestMethod]
        public async Task 多个限价单匹配()
        {
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidRes1 = await controller.BuyLimit("BTC/USDT", 1001, 1);
            var bidRes2 = await controller.BuyLimit("BTC/USDT", 1001.1m, 0.1m);
            var bidRes3 = await controller.BuyLimit("BTC/USDT", 1001.2m, 0.7m);
            var bidRes4 = await controller.BuyLimit("BTC/USDT", 1001.1m, 0.2m);

            // 下了4个买单的广播
            for (var i = 0; i < 4; ++i)
            {
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);
            }

            var quoteUsed = (1001 * 1 + 1001.1m * 0.1m + 1001.2m * 0.7m + 1001.1m * 0.2m);
            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var balance = db.AccountBalances.Single(b => b.Currency == "USDT" && b.AccountId == TestConstants.DEFAULT_BID_USER_ID);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE, balance.Balance);
                Assert.AreEqual(quoteUsed * (1 + TestConstants.DEFAULT_FEE_RATE), balance.Pending);

                balance = db.AccountBalances.Single(b => b.Currency == "BTC" && b.AccountId == TestConstants.DEFAULT_BID_USER_ID);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE, balance.Balance);
                Assert.AreEqual(0, balance.Pending);
            }

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes = await controller.SellLimit("BTC/USDT", 1001, 2);
            Bit.Art.MySqlDb.Program.s_autoResetEventForTest.WaitOne(1000);
            Bit.Art.Engine.Program.s_autoResetEventForTest.WaitOne(1000);

            // 还有成交一个卖单、并同时成交4个卖单的广播
            for (var i = 0; i < 5; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            var actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);
            
            Assert.IsTrue(Guid.TryParse(bidRes1.Result, out Guid bidid1));
            Assert.IsTrue(Guid.TryParse(bidRes2.Result, out Guid bidid2));
            Assert.IsTrue(Guid.TryParse(bidRes3.Result, out Guid bidid3));
            Assert.IsTrue(Guid.TryParse(bidRes4.Result, out Guid bidid4));
            Assert.IsTrue(Guid.TryParse(askRes.Result, out Guid askid));

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == askid);
                Assert.IsNotNull(order);
                Assert.AreEqual(0, order.VolumeRemaining);
                Assert.AreEqual(2, order.Volume);
                Assert.AreEqual(1001, order.Price.Value);

                foreach (var bid in new Guid[] { bidid1, bidid2, bidid3, bidid4 })
                {
                    order = db.Orders.SingleOrDefault(o => o.Id == bid);
                    Assert.IsNotNull(order);
                    Assert.AreEqual(0, order.VolumeRemaining);
                }

                var balance = db.AccountBalances.Single(b => b.Currency == "USDT" && b.AccountId == TestConstants.DEFAULT_BID_USER_ID);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - (quoteUsed * (1 + TestConstants.DEFAULT_FEE_RATE)), balance.Balance);
                Assert.AreEqual(0, balance.Pending);

                balance = db.AccountBalances.Single(b => b.Currency == "BTC" && b.AccountId == TestConstants.DEFAULT_BID_USER_ID);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 2, balance.Balance);
                Assert.AreEqual(0, balance.Pending);

                balance = db.AccountBalances.Single(b => b.Currency == "BTC" && b.AccountId == TestConstants.DEFAULT_ASK_USER_ID);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, balance.Balance);
                Assert.AreEqual(0, balance.Pending);

                balance = db.AccountBalances.Single(b => b.Currency == "USDT" && b.AccountId == TestConstants.DEFAULT_ASK_USER_ID);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + quoteUsed * (1 - TestConstants.DEFAULT_FEE_RATE), balance.Balance);
                Assert.AreEqual(0, balance.Pending);

                var cachedBalances = await s_Memcache.GetBalances(TestConstants.DEFAULT_BID_USER_ID, "USDT", "BTC");
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - (quoteUsed * (1 + TestConstants.DEFAULT_FEE_RATE)), cachedBalances.CoinBalances[0].Balance);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 2, cachedBalances.CoinBalances[1].Balance);

                cachedBalances = await s_Memcache.GetBalances(TestConstants.DEFAULT_ASK_USER_ID, "USDT", "BTC");
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + quoteUsed * (1 - TestConstants.DEFAULT_FEE_RATE), cachedBalances.CoinBalances[0].Balance);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, cachedBalances.CoinBalances[1].Balance);
            }
        }

        [TestMethod]
        public async Task 单个限价卖单多个限价买单部分匹配()
        {
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidRes1 = await controller.BuyLimit("BTC/USDT", 1001, 1);
            var bidRes2 = await controller.BuyLimit("BTC/USDT", 1001.1m, 0.1m);
            var bidRes3 = await controller.BuyLimit("BTC/USDT", 1001.2m, 0.7m);
            var bidRes4 = await controller.BuyLimit("BTC/USDT", 1001.1m, 0.1m);

            // 下了4个买单的广播
            for (var i = 0; i < 4; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes = await controller.SellLimit("BTC/USDT", 1001, 2);

            // 还有成交一个卖单、并同时成交4个卖单的广播
            for (var i = 0; i < 5; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(bidOpenOrdersResponse.Success);
            var bidOpens = bidOpenOrdersResponse.Result;
            Assert.AreEqual(0, bidOpens.Length);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(askOpenOrdersResponse.Success);
            var askOpens = askOpenOrdersResponse.Result;
            Assert.AreEqual(1, askOpens.Length);
            Assert.AreEqual(0.1m, askOpens[0].QuantityRemaining);
            Assert.AreEqual(2, askOpens[0].Quantity);
            Assert.AreEqual(1001, askOpens[0].Price);
            
            Assert.IsTrue(Guid.TryParse(bidRes1.Result, out Guid bidid1));
            Assert.IsTrue(Guid.TryParse(bidRes2.Result, out Guid bidid2));
            Assert.IsTrue(Guid.TryParse(bidRes3.Result, out Guid bidid3));
            Assert.IsTrue(Guid.TryParse(bidRes4.Result, out Guid bidid4));
            Assert.IsTrue(Guid.TryParse(askRes.Result, out Guid askid));

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == askid);
                Assert.IsNotNull(order);
                Assert.AreEqual(0.1m, order.VolumeRemaining);
                Assert.AreEqual(2, order.Volume);
                Assert.AreEqual(1001, order.Price.Value);

                foreach (var bid in new Guid[] { bidid1, bidid2, bidid3, bidid4 })
                {
                    order = db.Orders.SingleOrDefault(o => o.Id == bid);
                    Assert.IsNotNull(order);
                    Assert.AreEqual(0, order.VolumeRemaining);
                }

                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0.1m, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.9m, balance.Balance);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, balance.Balance);
            }

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var apiBalancesRes = await controller.GetBalance();
            var apiBalances = apiBalancesRes.Result;
            var apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, apiBalance.Balance);
            
            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0.1m, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.9m, apiBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, apiBalance.Balance);
        }

        [TestMethod]
        public async Task 单个限价买单多个限价卖单部分匹配()
        {
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes1 = await controller.SellLimit("BTC/USDT", 1001, 1);
            var askRes2 = await controller.SellLimit("BTC/USDT", 1001.1m, 0.1m);
            var askRes3 = await controller.SellLimit("BTC/USDT", 1001.2m, 0.8m);
            var askRes4 = await controller.SellLimit("BTC/USDT", 1001.1m, 0.1m);

            // 下了4个卖单的广播
            for (var i = 0; i < 4; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(askOpenOrdersResponse.Success);
            var askOpens = askOpenOrdersResponse.Result;
            Assert.AreEqual(4, askOpens.Length);
            Assert.IsTrue(askOpens.Any(o => o.Price == 1001.2m && o.Quantity == 0.8m));

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidRes = await controller.BuyLimit("BTC/USDT", 1001.2m, 1.9m);

            // 还有成交一个买单、并同时成交4个卖单的广播
            for (var i = 0; i < 5; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(bidOpenOrdersResponse.Success);
            var bidOpens = bidOpenOrdersResponse.Result;
            Assert.AreEqual(0, bidOpens.Length);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            askOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(askOpenOrdersResponse.Success);
            askOpens = askOpenOrdersResponse.Result;
            Assert.AreEqual(1, askOpens.Length);
            Assert.AreEqual(0.1m, askOpens[0].QuantityRemaining);
            Assert.AreEqual(0.8m, askOpens[0].Quantity);
            Assert.AreEqual(1001.2m, askOpens[0].Price);
            
            Assert.IsTrue(Guid.TryParse(askRes1.Result, out Guid askid1));
            Assert.IsTrue(Guid.TryParse(askRes2.Result, out Guid askid2));
            Assert.IsTrue(Guid.TryParse(askRes3.Result, out Guid askid3));
            Assert.IsTrue(Guid.TryParse(askRes4.Result, out Guid askid4));
            Assert.IsTrue(Guid.TryParse(bidRes.Result, out Guid bidid));

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == bidid);
                Assert.IsNotNull(order);
                Assert.AreEqual(0, order.VolumeRemaining);
                Assert.AreEqual(1.9m, order.Volume);
                Assert.AreEqual(1001.2m, order.Price.Value);

                foreach (var bid in new Guid[] { askid1, askid2, askid4 })
                {
                    order = db.Orders.SingleOrDefault(o => o.Id == bid);
                    Assert.IsNotNull(order);
                    Assert.AreEqual(0, order.VolumeRemaining);
                }

                order = db.Orders.SingleOrDefault(o => o.Id == askid3);
                Assert.IsNotNull(order);
                Assert.AreEqual(0.1m, order.VolumeRemaining);
                Assert.AreEqual(0.8m, order.Volume);
                Assert.AreEqual(1001.2m, order.Price.Value);

                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0.1m, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.9m, balance.Balance);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, balance.Balance);
            }

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var apiBalancesRes = await controller.GetBalance();
            var apiBalances = apiBalancesRes.Result;
            var apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 1902.06m * 0.999m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0.1m, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.9m, apiBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 1902.06m * 1.001m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.9m, apiBalance.Balance);
        }

        [TestMethod]
        public async Task 多个限价卖单多个限价买单全部匹配()
        {
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidRes1 = await controller.BuyLimit("BTC/USDT", 1001, 1);
            var bidRes2 = await controller.BuyLimit("BTC/USDT", 1001.1m, 0.2m);
            var bidRes3 = await controller.BuyLimit("BTC/USDT", 1001.2m, 0.7m);
            var bidRes4 = await controller.BuyLimit("BTC/USDT", 1001.1m, 0.1m);

            // 下了4个买单的广播
            for (var i = 0; i < 4; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes1 = await controller.SellLimit("BTC/USDT", 1001.1m, 0.8m);
            // 成交一个卖单、并同时成交3个买单的广播
            for (var i = 0; i < 3; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(bidOpenOrdersResponse.Success);
            var bidOpens = bidOpenOrdersResponse.Result;
            Assert.IsTrue(bidOpens.Length == 3 || bidOpens.Length == 4);
            Assert.IsTrue(bidOpens.Any(b => b.Price == 1001.1m && b.QuantityRemaining == 0.1m && b.Quantity == 0.2m));
            Assert.IsTrue(bidOpens.Any(b => b.Price == 1001 && b.QuantityRemaining == 1));

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(askOpenOrdersResponse.Success);
            var askOpens = askOpenOrdersResponse.Result;
            Assert.AreEqual(0, askOpens.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 800.95m * 0.999m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 800.95m * 0.999m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 0.8m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 0.8m, balance.Balance);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(1201.22m * 1.001m, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 2002.17m * 1.001m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 800.95m * 1.001m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 0.8m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 0.8m, balance.Balance);
            }

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var apiBalancesRes = await controller.GetBalance();
            var apiBalances = apiBalancesRes.Result;
            var apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 800.95m * 0.999m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 800.95m * 0.999m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 0.8m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 0.8m, apiBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(1201.22m * 1.001m, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 2002.17m * 1.001m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 800.95m * 1.001m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 0.8m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 0.8m, apiBalance.Balance);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes2 = await controller.SellLimit("BTC/USDT", 1001, 1.2m);
            // 成交一个卖单、并同时成交3个买单的广播
            for (var i = 0; i < 4; ++i)
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            bidOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(bidOpenOrdersResponse.Success);
            bidOpens = bidOpenOrdersResponse.Result;
            Assert.AreEqual(0, bidOpens.Length);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            askOpenOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(askOpenOrdersResponse.Success);
            askOpens = askOpenOrdersResponse.Result;
            Assert.AreEqual(0, askOpens.Length);
            
            Assert.IsTrue(Guid.TryParse(bidRes1.Result, out Guid bidid1));
            Assert.IsTrue(Guid.TryParse(bidRes2.Result, out Guid bidid2));
            Assert.IsTrue(Guid.TryParse(bidRes3.Result, out Guid bidid3));
            Assert.IsTrue(Guid.TryParse(bidRes4.Result, out Guid bidid4));
            Assert.IsTrue(Guid.TryParse(askRes1.Result, out Guid askid1));
            Assert.IsTrue(Guid.TryParse(askRes2.Result, out Guid askid2));

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var order = db.Orders.SingleOrDefault(o => o.Id == askid1);
                Assert.IsNotNull(order);
                Assert.AreEqual(0, order.VolumeRemaining);
                Assert.AreEqual(0.8m, order.Volume);
                Assert.AreEqual(1001.1m, order.Price.Value);

                order = db.Orders.SingleOrDefault(o => o.Id == askid2);
                Assert.IsNotNull(order);
                Assert.AreEqual(0, order.VolumeRemaining);
                Assert.AreEqual(1.2m, order.Volume);
                Assert.AreEqual(1001, order.Price.Value);

                foreach (var bid in new Guid[] { bidid1, bidid2, bidid3, bidid4 })
                {
                    order = db.Orders.SingleOrDefault(o => o.Id == bid);
                    Assert.IsNotNull(order);
                    Assert.AreEqual(0, order.VolumeRemaining);
                }

                var balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 2002.17m * 0.999m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 2002.17m * 0.999m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_ASK_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, balance.Balance);

                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 2002.17m * 1.001m, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 2002.17m * 1.001m, balance.Balance);
                balance = db.AccountBalances.Single(a => a.AccountId == TestConstants.DEFAULT_BID_USER_ID && a.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 2, balance.Available);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 2, balance.Balance);
            }

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 2002.17m * 0.999m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 2002.17m * 0.999m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 2, apiBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "USDT");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 2002.17m * 1.001m, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 2002.17m * 1.001m, apiBalance.Balance);

            apiBalance = apiBalances.Single(b => b.Coin == "BTC");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 2, apiBalance.Available);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 2, apiBalance.Balance);
        }

        [TestMethod]
        public async Task 验证多笔订单成交后的余额变化()
        {
            var controller = new MarketController();
            controller.SetMemcache(s_Memcache);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bid = await controller.BuyLimit("BTC/USDT", 10000, 1);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var ask = await controller.SellLimit("BTC/USDT", 10000, 1);
            for (var i = 0; i < 2; ++i)
                Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);

            // 1. 验证缓存中的挂单已经清空
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            var actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var conn = db.Database.GetDbConnection();
                conn.Open();

                // 2. 验证所有订单完全成交
                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "SELECT COUNT(*) FROM exchangedb.orders WHERE VolumeRemaining > 0";
                    var notfilled = (long)cmd.ExecuteScalar();
                    Assert.AreEqual(0, notfilled);

                }

                // 3. 验证数据库里的余额是正确的
                var balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                // 扣除0.1%的手续费
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 10000 * 1.001m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 10000 * 0.999m, balance.Balance);
            }

            // 4. 验证缓存中的余额是正确的
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var apiBalancesRes = await controller.GetBalance();
            var balances = apiBalancesRes.Result;
            var actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - 10000 * 1.001m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 10000 * 0.999m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            bid = await controller.BuyLimit("BTC/USDT", 10001, 0.5m);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            ask = await controller.SellLimit("BTC/USDT", 9999.9m, 0.3m);
            ask = await controller.SellLimit("BTC/USDT", 9999m, 0.2m);
            for (var i = 0; i < 3; ++i)
                Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);

            // 1. 验证缓存中的挂单已经清空
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var conn = db.Database.GetDbConnection();
                conn.Open();

                // 2. 验证所有订单完全成交
                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "SELECT COUNT(*) FROM exchangedb.orders WHERE VolumeRemaining > 0";
                    var notfilled = (long)cmd.ExecuteScalar();
                    Assert.AreEqual(0, notfilled);

                }

                // 3. 验证数据库里的余额是正确的
                var balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                // 扣除0.1%的手续费
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.5m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - (10000 + 10001 * 0.3m + 10001 * 0.2m) * 1.001m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.5m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + (10000 + 10001 * 0.3m + 10001 * 0.2m) * 0.999m, balance.Balance);
            }

            // 4. 验证缓存中的余额是正确的
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.5m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - (10000 + 10001 * 0.3m + 10001 * 0.2m) * 1.001m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.5m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + (10000 + 10001 * 0.3m + 10001 * 0.2m) * 0.999m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            bid = await controller.BuyLimit("BTC/USDT", 10002, 0.2m);
            bid = await controller.BuyLimit("BTC/USDT", 10002.1m, 1);
            ask = await controller.BuyLimit("BTC/USDT", 10002, 0.8m);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            ask = await controller.SellLimit("BTC/USDT", 9998, 2);
            for (var i = 0; i < 4; ++i)
                Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);

            // 1. 验证缓存中的挂单已经清空
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var conn = db.Database.GetDbConnection();
                conn.Open();

                // 2. 验证所有订单完全成交
                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "SELECT COUNT(*) FROM exchangedb.orders WHERE VolumeRemaining > 0";
                    var notfilled = (long)cmd.ExecuteScalar();
                    Assert.AreEqual(0, notfilled);
                }

                // 3. 验证数据库里的余额是正确的
                var balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                // 扣除0.1%的手续费
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 1.5m + 2, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m) * 1.001m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 1.5m - 2, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m) * 0.999m, balance.Balance);
            }

            // 4. 验证缓存中的余额是正确的
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 3.5m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE - (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m) * 1.001m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 3.5m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m) * 0.999m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            bid = await controller.BuyLimit("BTC/USDT", 10003, 0.2m);
            bid = await controller.BuyLimit("BTC/USDT", 10003.1m, 1);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            ask = await controller.SellLimit("BTC/USDT", 9997, 1);
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            bid = await controller.BuyLimit("BTC/USDT", 10000, 0.8m);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            ask = await controller.SellLimit("BTC/USDT", 9996, 1);
            for (var i = 0; i < 5; ++i)
                Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);

            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);
            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            openOrdersResponse = await controller.GetOpenOrders("BTC/USDT");
            Assert.IsTrue(openOrdersResponse.Success);
            actual = openOrdersResponse.Result;
            Assert.AreEqual(0, actual.Length);

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var conn = db.Database.GetDbConnection();
                conn.Open();
                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "SELECT COUNT(*) FROM exchangedb.orders WHERE VolumeRemaining > 0";
                    var notfilled = (long)cmd.ExecuteScalar();
                    Assert.AreEqual(0, notfilled);

                }

                var balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                // 扣除0.1%的手续费
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 5.5m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_BID_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE -
                    (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m + 10003 * 0.2m + 10003.1m + 10000 * 0.8m) * 1.001m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "BTC");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 5.5m, balance.Balance);

                balance = db.AccountBalances.Single(b => b.AccountId == TestConstants.DEFAULT_ASK_USER_ID && b.Currency == "USDT");
                Assert.AreEqual(0, balance.Pending);
                Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE +
                    (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m + 10003 * 0.2m + 10003.1m + 10000 * 0.8m) * 0.999m, balance.Balance);
            }

            // 4. 验证缓存中的余额是正确的
            controller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE + 5.5m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE -
                (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m + 10003 * 0.2m + 10003.1m + 10000 * 0.8m) * 1.001m, actualBalance.Balance);

            controller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            apiBalancesRes = await controller.GetBalance();
            balances = apiBalancesRes.Result;
            actualBalance = balances.Single(b => string.Compare(b.Coin, "BTC", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_BTC_BALANCE - 5.5m, actualBalance.Balance);
            actualBalance = balances.Single(b => string.Compare(b.Coin, "USDT", true) == 0);
            Assert.AreEqual(0, actualBalance.Pending);
            Assert.AreEqual(DbInitializer.INIT_USDT_BALANCE + 
                (10000 + 10001 * 0.5m + 10002 * 0.2m + 10002.1m + 10002 * 0.8m + 10003 * 0.2m + 10003.1m + 10000 * 0.8m) * 0.999m, actualBalance.Balance);
        }
    }
#endif
}
